<template>
  <div class="uploader">
    <input
      ref="fileInput"
      type="file"
     :accept="accept"
      multiple
      @change="onChangeFileHandler"
    />
    <span class="upload-btn-wrap">
      <span class="upload-btn" @click="handleUpload">+ 上传素材</span>
    </span>
  </div>
</template>

<script>
import { genUUID } from '@/lib/uuid';
import QiniuKodo from './core/qiniu-kodo.js';
import {
  getAcceptByCategory,
  getFileExtension,
  getFileCategory,
  inspectFileType,
  formatFileSize
} from './core/helper.js';

/**
 * acceptTypes: 限制上传的文件类型
 * acceptSizes: 限制上传的文件大小
 */
export default {
  props: {
    acceptTypes: {
      type: String,
      default: ''
    },
    acceptSizes: Object,
    currType: {
      type: String,
      default: 'all'
    }
  },
  data() {
    return {
      uploadClient: null,
      authority: null, // 权限，有时效性

      fileQueue: [] // 文件上传队列
    }
  },

  computed: {
    accept() {
      // 如果传入了 subCategory 则走 subCategory 限制逻辑，否则采用默认按 category 做限制
      if (this.acceptTypes) {
        return this.acceptTypes;
      }

      return getAcceptByCategory(this.currType);
    }
  },

  watch: {
    fileQueue: {
      handler(queue) {
        const hasUploading = queue.some(item => item.status === 1);

        if (hasUploading === false) {
          this.moveNext();
        }

        this.$emit('update-queue', queue);
      },
      deep: true
    }
  },

  methods: {
    // 获取文件上传所需的 Token
    getUploadToken() {
      return this.$store
        .dispatch('proxyAction', {
          name: 'getMaterialUploadToken',
          message: false
        })
        .then(({ status: { code, message }, data }) => {
          if (code === 200) {
            return data;
          }
        })
        .catch(() => {
          this.$emit('on-error', {
            code: 20001,
            message: 'Failed to get upload token.'
          });
        });
    },

    // 重新上传
    reUploadByKey(key) {
      const order = this.fileQueue.findIndex(item => item.key === key);
      if (order !== -1) {
        const uploadFile = this.fileQueue[order];
        uploadFile.status = 0;
        this.fileQueue.splice(order, 1, uploadFile);
      }
    },

    // 从队列中移除文件
    removeUploadFileFromTheQueue(key) {
      const order = this.fileQueue.findIndex(item => item.key === key);
      if (order !== -1) {
        if (this.fileQueue[order].status === 1) {
          this.cancelUpload();
        }
        this.fileQueue.splice(order, 1);
      }
    },
    cancelUpload() {
      if (this.uploadClient) {
        this.uploadClient.abort();
      }
    },
    // 指针位移
    moveNext() {
      const order = this.fileQueue.findIndex(item => item.status === 0);
      const uploadFileItem = this.fileQueue[order];

      if (uploadFileItem) {
        uploadFileItem.status = 1;
        this.fileQueue.splice(order, 1, uploadFileItem);
        this.upload(uploadFileItem);
      }
    },

    // 生成要放入队列中的文件结构
    generateUploadFileItem(file) {
      const uuidKey = genUUID();
      const saveKey = `${this.authority.uploadDate}${uuidKey}.${getFileExtension(file.name)}`;

      const uploadFileItem = {
        progress: 0,
        status: 0,
        key: uuidKey,
        saveKey,
        file
      }

      // 检测文件大小是否超出上限
      const error = this.checkFileSize(file);
      if (error) {
        uploadFileItem.status = error.code;
        uploadFileItem.statusMessage = error.message;
      }

      return uploadFileItem;
    },
    checkFileSize(file) {
      const category = getFileCategory(file.name); // document | audio | video | img | other
      if (category && this.acceptSizes[category]) {
        const maxSize = parseInt(this.acceptSizes[category]);
        if (file.size > maxSize) {
          return {
            message: `上传失败，文件大小不能超过${formatFileSize(maxSize)}`,
            code: 3
          }
        }
      }

      return false;
    },

    onChangeFileHandler(e) {
      const files = e.currentTarget.files;
      // 上传的文件类型不匹配，拒绝上传
      const filesExtension = Array.prototype.map.call(files, file => getFileExtension(file.name));
      const inspectInfo = inspectFileType(this.accept, filesExtension);
      if (inspectInfo) {
        e.target.value = '';
        return this.$emit('on-rejected', [...inspectInfo].join(','));
      }

      // 更新队列
      this.fileQueue = this.fileQueue.concat(Array.prototype.map.call(files, file => this.generateUploadFileItem(file)));
      e.target.value = '';
    },
    async handleUpload() {
      if (this.authority === null) {
        try {
          this.authority = await this.getUploadToken();
        } catch (error) {
          this.$emit('on-error', {
            code: 20002,
            message: error
          });
          return false;
        }
      }

      this.$refs.fileInput.click();
    },

    // 开始上传
    async upload(uploadFile) {
      const { file, key, saveKey } = uploadFile;
      const callback = {
        updateProgress: this.updateProgress,
        onSuccess: this.onSuccess,
        onError: this.onError
      };

      if (this.authority === null) {
        this.authority = await this.getUploadToken();
      }

      if (this.uploadClient === null) {
        this.uploadClient = new QiniuKodo(this.authority.token);
      }

      this.uploadClient.run(file, key, saveKey, callback);
      this.$emit('update-file', uploadFile);
    },

    updateFileQueueItem(key, status, progress) {
      const order = this.fileQueue.findIndex(item => item.key === key);
      const uploadFileItem = this.fileQueue[order];

      if (uploadFileItem) {
        uploadFileItem.progress = progress ? Math.floor(progress * 100) / 100 : 0;
        uploadFileItem.status = status;
        if (status === 2) {
          this.fileQueue.splice(order, 1);
        } else {
          this.fileQueue.splice(order, 1, uploadFileItem);
        }
      }
    },
    // 更新进度
    updateProgress(payload) {
      const { key, percent } = payload;
      this.updateFileQueueItem(key, 1, percent);
    },
    // 上传成功
    onSuccess(payload) {
      const { key, response } = payload; // response: { hash, key, name, size }

      this.$store
        .dispatch('proxyAction', {
          name: 'publishMaterial',
          message: false,
          data: {
            materialFullName: response.name,
            materialHash: response.hash,
            materialSize: response.size,
            materialUrl: response.key
          }
        })
        .then(() => {
          this.$emit('on-success');
          this.updateFileQueueItem(key, 2);
        })
        .catch(error => {
          this.$emit('on-error', {
            code: 40001,
            message: error
          });
          this.updateFileQueueItem(key, 3);
        })
    },
    // 上传失败
    onError(payload) {
      const { key, error } = payload;
      this.updateFileQueueItem(key, 3);

      if (error.code === 401) { // 401. token invalid
        this.uploadClient = null;
        this.authority = null;
      }

      this.$emit('on-error', {
        code: 30001,
        message: 'There was an error uploading this file, and please try again.'
      })
    }
  },

  unmounted() {
    this.uploadClient = null;
    this.authority = null;
  }
}
</script>

<style lang="scss" scoped>
.uploader {
  display: inline-block;
  cursor: pointer;
  user-select: none;

  .upload-btn-wrap {
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .upload-btn {
    padding: 7px 22px;
    line-height: 14px;
    font-size: 14px;
    color: #5293F5;
    border: 1px solid #5293F5;
    border-radius: 4px;
  }

  input[type="file"] {
    display: none;
  }
}
</style>